#include <pybind11/pybind11.h>
#include <pybind11/numpy.h>
#include <pybind11/stl.h>
#include <DataCore/buffer_data.h>
#include <Utilities/SpaceHash/neighbor_list.h>
#include <glm/glm.hpp>
#include <string>
using namespace PhysLeo;
namespace py = pybind11;

template<typename T>
static void declareBufferData(py::module &mod, const std::string &typestr) 
{
	using class_type = BufferData<T>;
	auto pyclass_name = std::string("BufferData") + typestr;
	py::class_<class_type, std::shared_ptr<class_type>>cls_buffer_data(mod, pyclass_name.c_str(), py::buffer_protocol());

    std::string data_type;
    if (typestr == "F")data_type = "float";
    else if (typestr == "D")data_type = "double";
    else if (typestr == "I")data_type = "int";
    else if (typestr == "3F")data_type = "float3";
    else if (typestr == "3D")data_type = "double3";
    else if (typestr == "NL")data_type = "NeighborList";
    cls_buffer_data.doc() = "a class which manage CPU buffer and GPU buffer simultaneously, data type is " + data_type;

	cls_buffer_data.def(py::init<int>(), R"pbdoc(
        __init__(size: int)  -> None 
                
            initialize the buffer data object by setting its size, note it has't allocate any CPU buffer or GPU buffer for this buffer data object now. 

            :param int size: the size of buffer data object
            :return: None
            :rtype: None

    )pbdoc");

	cls_buffer_data.def("size", &class_type::size, R"pbdoc(
        size() -> int

            return the size of the buffer data object.

            :return: the size of the buffer data object
            :rtype: int    
        
    )pbdoc");

	cls_buffer_data.def("allocateCpu", &class_type::allocateCpu, R"pbdoc(

        allocateCpu() -> None

            allocate cpu buffer for this buffer data object.

            :return: None
            :rtype: None

    )pbdoc");

	cls_buffer_data.def("allocateGpu", &class_type::allocateGpu, R"pbdoc(

        allocateGpu() -> None

            allocate gpu buffer for this buffer data object.

            :return: None
            :rtype: None

    )pbdoc");

	cls_buffer_data.def("setZeroCpu", &class_type::setZeroCpu, R"pbdoc(

        setZeroCpu() -> None

            reset cpu buffer data to zero

            :return: None
            :rtype: None

    )pbdoc");

	cls_buffer_data.def("setZeroGpu", &class_type::setZeroGpu, R"pbdoc(

        setZeroGpu() -> None

            reset gpu buffer data to zero

            :return: None
            :rtype: None

    )pbdoc");

	cls_buffer_data.def("cpu2Gpu", &class_type::cpu2Gpu, R"pbdoc(

        cpu2Gpu() -> None

            copy cpu buffer data to gpu buffer

            :return: None
            :rtype: None

    )pbdoc");

	cls_buffer_data.def("gpu2Cpu", &class_type::gpu2Cpu, R"pbdoc(

        gpu2Cpu() -> None

            copy gpu buffer data to cpu buffer

            :return: None
            :rtype: None

    )pbdoc");

    if(typestr == "3F")
    {
        cls_buffer_data.def_buffer([](class_type &buff)->py::buffer_info {
            return py::buffer_info(
                buff.ptrCpu(),                                              
                sizeof(float),
                py::format_descriptor<float>::format(),
                2,
                { buff.ptrCpu() != nullptr ? buff.size() : 0, 3 },
                { sizeof(float) * 3, sizeof(float) }
            );
        });
    }
    else if(typestr == "3D")
    {
        cls_buffer_data.def_buffer([](class_type &buff)->py::buffer_info {
            return py::buffer_info(
                buff.ptrCpu(),
                sizeof(double),
                py::format_descriptor<double>::format(),
                2,
                { buff.ptrCpu() != nullptr ? buff.size() : 0, 3 },
                { sizeof(double) * 3, sizeof(double) }
                );
        });
    }
    else if(typestr == "NL")
    {
        cls_buffer_data.def_buffer([](class_type &buff)->py::buffer_info {
            return py::buffer_info(
                buff.ptrCpu(),
                sizeof(int),
                py::format_descriptor<int>::format(),
                2,
                { buff.ptrCpu() != nullptr ? buff.size() : 0, 1+ SPACE_HASH_MAX_NEIGHBOR },
                { sizeof(int) * (1+SPACE_HASH_MAX_NEIGHBOR), sizeof(int) }
            );
        });
    }
    else
    {
        cls_buffer_data.def_buffer([](class_type &buff)->py::buffer_info {
            return py::buffer_info(buff.ptrCpu(), buff.ptrCpu() != nullptr ? buff.size() : 0);
        });
    }
		
}

void initBufferData(py::module &m)
{
	declareBufferData<float>(m, std::string("F"));
	declareBufferData<double>(m, std::string("D"));
	declareBufferData<int>(m, std::string("I"));
    declareBufferData<glm::tvec3<float>>(m, std::string("3F"));
    declareBufferData<glm::tvec3<double>>(m, std::string("3D"));
    declareBufferData<NeighborList>(m, std::string("NL"));
}
